package org.raymond.iworks.study.redis.redisson;

import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author raymond
 * @version V1.0
 * @Description: TODO
 */
@Slf4j
/**
 * Redisson中的spinLock默认使用指数退避策略（Exponential Backoff strategy）进行重试。
 * 指数退避策略是一种等待时间的策略，当某个操作失败时，等待时间会以指数级别递增。这样做的好处是，每次重试之间的等待时间都会大幅度增加，可以减少对Redis服务器的频繁请求，避免网络拥塞和服务器负载过高。
 * 在Redisson中，spinLock的默认指数退避策略是按照以下规则进行的：
 * 第一次尝试操作，等待时间是0毫秒。
 * 如果操作失败，等待时间会乘以2，例如第二次尝试等待时间是0.002秒，第三次尝试等待时间是0.004秒，以此类推。
 * 等待时间最大值为10毫秒。如果超过最大等待时间仍然无法获取锁，则放弃重试，并返回锁定失败。
 * 通过使用指数退避策略，Redisson可以自动地进行重试，减少对Redis服务器的频繁请求，并且避免了对同一个键的并发访问冲突。
 */
public class SpinLockTest {
    public static void main(String[] args) throws Exception {
        test2();
    }

    // 短时间内获取或释放数千个锁
    public static void test1() throws Exception {
        RedissonClient c = client();
        RLock lock = c.getSpinLock("mySpin");
        ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 3,
                0, TimeUnit.SECONDS, new LinkedBlockingQueue());
        Runnable r1 = ()->{
            log.info("wait lock...");
            lock.lock();
            log.info("do something");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            lock.unlock();
            log.info("release lock");
        };
        Runnable r2 = ()->{
            log.info("wait lock...");
            lock.lock(1500, TimeUnit.MILLISECONDS);
            log.info("do something");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            lock.unlock();
            log.info("release lock");
        };
        Runnable r3 = ()->{
            try {
                log.info("wait lock...");
                lock.tryLock(3000, 3000, TimeUnit.MILLISECONDS);
                log.info("do something");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            } finally {
                lock.unlock();
                log.info("release lock");
            }
        };
        pool.execute(r1);
        pool.execute(r2);
        pool.execute(r3);

        log.info("pool shutdown but wait thread");
        pool.shutdown();
        Thread.sleep(3500);

        log.info("shutdown");
        c.shutdown();
    }

    public static void test2() throws Exception{
        RedissonClient c = client();
        RLock lock = c.getSpinLock("mySpin");
        int size = 100;
        ThreadPoolExecutor pool = new ThreadPoolExecutor(size, size,
                0, TimeUnit.SECONDS, new LinkedBlockingQueue());
        Runnable r = ()->{
            log.info("wait lock...");
            lock.lock();
            log.info("do something");
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
            }
            lock.unlock();
            log.info("release lock");
        };

        for(int i=0; i<size; i++)
            pool.submit(r);

        log.info("pool shutdown but wait thread");
        pool.shutdown();
        Thread.sleep(size*10+2000);

        log.info("shutdown");
        c.shutdown();
    }

    public static RedissonClient client(){
        Config cfg = new Config();
        cfg.useSingleServer().setAddress("redis://192.168.0.125:6379");
        RedissonClient client = Redisson.create(cfg);
        return client;
    }
}
